﻿using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Model.DataManagement;
using Model.Entity;

namespace LibraryManagerTests.Model.DataManagement
{

    /// <summary>
    /// Subclassed to make internal fields available for testing.
    /// </summary>
    class TestableBookManager : BookManager
    {
        public static Int32 FirstId { get { return FIRST_ID; } }

        public IList<Book> Entities { get { return _entities; } }

        public IList<Book> Edited { get { return _edited; } }

        public IList<Book> New { get { return _new; } }

        public IList<Book> Deleted { get { return _deleted; } }

        public IList<Book> Managed { get { return _managed; } }

    }

    /// <summary>
    /// This class contains unit tests for Model.DataManagement.BookManager.
    /// </summary>
    [TestClass]
    public class BookManagerTests
    {

        /// <summary>
        /// Tests BookManager.CanSave().
        /// </summary>
        [TestMethod]
        public void TestCanSave()
        {
            // obtaining references for less text
            var authorMax = BookManager.MAX_AUTHOR_LENGTH;
            var authorErr = BookManager.AUTHOR_LEN_ERROR_HU;
            var titleMax = BookManager.MAX_TITLE_LENGTH;
            var titleErr = BookManager.TITLE_LEN_ERROR_HU;
            var publisherMax = BookManager.MAX_PUBLISHER_LENGTH;
            var publisherErr = BookManager.PUBLISHER_LEN_ERROR_HU;
            var isbnMax = BookManager.MAX_ISBN_LENGTH;
            var isbnErr = BookManager.ISBN_LEN_ERROR_HU;
            var yearErr = BookManager.YEAR_ERROR_HU;
            var editionMin = BookManager.MIN_EDITION_VALUE;
            var editionErr = BookManager.EDITION_VALUE_ERR_HU;

            BookManager bm = new BookManager();
            Book b = new Book(1);
            Messages msg;

            //
            // author length validation
            //

            // valid authors
            for (int i = 0; i <= authorMax; i++)
            {
                b.Author = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid authors
            for (int i = authorMax + 1; i < authorMax * 2; i++)
            {
                b.Author = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);
                string expectedMsg = string.Format(authorErr, i, authorMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // title length validation
            //
            b.Author = "";

            // valid titles
            for (int i = 0; i <= titleMax; i++)
            {
                b.Title = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid titles
            for (int i = titleMax + 1; i < titleMax * 2; i++)
            {
                b.Title = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);
                string expectedMsg = string.Format(titleErr, i, titleMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // publisher length validation
            //
            b.Title = "";

            // valid publishers
            for (int i = 0; i <= publisherMax; i++)
            {
                b.Publisher = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid publishers
            for (int i = publisherMax + 1; i < publisherMax * 2; i++)
            {
                b.Publisher = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);
                string expectedMsg = string.Format(publisherErr, i, publisherMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // ISBN length validation
            //
            b.Publisher = "";

            // valid ISBNs
            for (int i = 0; i <= isbnMax; i++)
            {
                b.Isbn = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid ISBNs
            for (int i = isbnMax + 1; i < isbnMax * 2; i++)
            {
                b.Isbn = string.Concat(Enumerable.Repeat("x", i));
                msg = bm.CanSave(b);
                string expectedMsg = string.Format(isbnErr, i, isbnMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // year length validation
            //
            b.Isbn = "";

            b.Year = DateTime.Now - new TimeSpan(367, 0, 0, 0);
            msg = bm.CanSave(b);
            Assert.AreEqual(0, msg.MessageList.Count);

            b.Year = DateTime.Now;
            msg = bm.CanSave(b);
            Assert.AreEqual(0, msg.MessageList.Count);

            b.Year = DateTime.Now + new TimeSpan(367, 0, 0, 0);
            msg = bm.CanSave(b);
            Assert.AreEqual(1, msg.MessageList.Count);
            Assert.IsTrue(msg.MessageList.Contains(yearErr));

            //
            // edition validation
            //
            b.Year = null;

            b.Edition = 0;
            msg = bm.CanSave(b);
            Assert.AreEqual(1, msg.MessageList.Count);
            Assert.IsTrue(msg.MessageList.Contains(string.Format(editionErr, editionMin)));

            b.Edition = editionMin;
            msg = bm.CanSave(b);
            Assert.AreEqual(0, msg.MessageList.Count);

        }

        /// <summary>
        /// Tests BookManager.Marshal() and Unmarshal().
        /// </summary>
        [TestMethod]
        public void TestMarshalUnmarshal()
        {
            int tests = 1000;
            BookManager bm = new BookManager();
            Book b;
            Book u; // unmarshalled
            Random r = new Random();
            RandomUnicodeString randUnicodeStr = new RandomUnicodeString();

            /*
             * Tests by fields
             */

            // id
            Int32 id;
            for (int i = 0; i < tests; i++)
            {
                id = r.Next(Int32.MinValue, Int32.MaxValue);
                b = new Book(id);
                Assert.AreEqual(id, bm.Unmarshal(bm.Marshal(b)).GetId());
            }

            // simpleId
            string simpleId;
            var maxSimpleId = BookManager.SIMPLEID_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                simpleId = randUnicodeStr.GetString(r.Next(maxSimpleId * 2));
                b = new Book(0, "", "", "", null, 0, "", simpleId, false);
                u = bm.Unmarshal(bm.Marshal(b));
                if (simpleId.Length > maxSimpleId) { simpleId = simpleId.Substring(0, maxSimpleId); }
                Assert.AreEqual(simpleId, u.SimpleId);
            }

            // author
            string author;
            var maxAuthor = BookManager.MAX_AUTHOR_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                author = randUnicodeStr.GetString(r.Next(maxAuthor * 2));
                b = new Book(0);
                b.Author = author;
                u = bm.Unmarshal(bm.Marshal(b));
                if (author.Length > maxAuthor) { author = author.Substring(0, maxAuthor); }
                Assert.AreEqual(author, u.Author);
            }

            // title
            string title;
            var maxTitle = BookManager.MAX_TITLE_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                title = randUnicodeStr.GetString(r.Next(maxTitle * 2));
                b = new Book(0);
                b.Title = title;
                u = bm.Unmarshal(bm.Marshal(b));
                if (title.Length > maxTitle) { title = title.Substring(0, maxTitle); }
                Assert.AreEqual(title, u.Title);
            }

            // publisher
            string publisher;
            var maxPublisher = BookManager.MAX_PUBLISHER_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                publisher = randUnicodeStr.GetString(r.Next(maxPublisher * 2));
                b = new Book(0);
                b.Publisher = publisher;
                u = bm.Unmarshal(bm.Marshal(b));
                if (publisher.Length > maxPublisher) { publisher = publisher.Substring(0, maxPublisher); }
                Assert.AreEqual(publisher, u.Publisher);
            }

            // year
            DateTime? year;

            for (int i = 0; i < tests; i++)
            {
                if (r.Next() % 2 == 0) { year = new DateTime(r.Next(1, 9999)); }
                else { year = null; }

                b = new Book(0);
                b.Year = year;
                u = bm.Unmarshal(bm.Marshal(b));

                if (null == year) { Assert.IsNull(b.Year); }
                else { Assert.AreEqual(year.Value.Year, u.Year.Value.Year); }
            }

            // edition
            ushort? edition;

            for (int i = 0; i < tests; i++)
            {
                b = new Book(0);
                if (r.Next() % 3 == 0) { edition = null; }
                else { edition = (ushort) (r.Next(10000) + BookManager.MIN_EDITION_VALUE); }
                b.Edition = edition;

                u = bm.Unmarshal(bm.Marshal(b));

                if (null == edition) { Assert.IsNull(u.Edition); }
                else { Assert.AreEqual(edition, u.Edition); }
            }

            // ISBN
            string isbn;
            var maxIsbn = BookManager.MAX_ISBN_LENGTH;

            for (int i = 0; i < tests; i++)
            {
                isbn = randUnicodeStr.GetString(r.Next(maxIsbn * 2));
                b = new Book(0);
                b.Isbn = isbn;
                u = bm.Unmarshal(bm.Marshal(b));
                if (isbn.Length > maxIsbn) { isbn = isbn.Substring(0, maxIsbn); }
                Assert.AreEqual(isbn, u.Isbn);
            }

            // borrowable
            bool borrowable = true;
            for (int i = 0; i < tests; i++)
            {
                if (r.Next() % 2 == 0) { borrowable ^= true; }
                b = new Book(0);
                b.Borrowable = borrowable;
                Assert.AreEqual(borrowable, bm.Unmarshal(bm.Marshal(b)).Borrowable);
            }


            /*
             * Compound fields test
             */

            byte[] m; // marshalled
            for (int i = 0; i < tests; i++)
            {
                if (r.Next() % 2 == 0) { borrowable ^= true; }
                isbn = randUnicodeStr.GetString(r.Next(maxIsbn * 2));
                if (r.Next() % 3 == 0) { edition = null; }
                else { edition = (ushort) (r.Next(10000) + BookManager.MIN_EDITION_VALUE); }
                if (r.Next() % 2 == 0) { year = new DateTime(r.Next(1, 9999)); }
                else { year = null; }
                publisher = randUnicodeStr.GetString(r.Next(maxPublisher * 2));
                title = randUnicodeStr.GetString(r.Next(maxTitle * 2));
                author = randUnicodeStr.GetString(r.Next(maxAuthor * 2));
                simpleId = randUnicodeStr.GetString(r.Next(maxSimpleId * 2));
                id = r.Next(Int32.MinValue, Int32.MaxValue);

                b = new Book(id, author, title, publisher, year, edition, isbn, simpleId, borrowable);
                m = bm.Marshal(b);
                u = bm.Unmarshal(m);

                Assert.AreEqual(id, u.GetId());
                if (simpleId.Length > maxSimpleId) { simpleId = simpleId.Substring(0, maxSimpleId); }
                Assert.AreEqual(simpleId, u.SimpleId);
                if (author.Length > maxAuthor) { author = author.Substring(0, maxAuthor); }
                Assert.AreEqual(author, u.Author);
                if (title.Length > maxTitle) { title = title.Substring(0, maxTitle); }
                Assert.AreEqual(title, u.Title);
                if (publisher.Length > maxPublisher) { publisher = publisher.Substring(0, maxPublisher); }
                Assert.AreEqual(publisher, u.Publisher);
                if (null == year) { Assert.IsNull(b.Year); }
                else { Assert.AreEqual(year.Value.Year, u.Year.Value.Year); }
                if (null == edition) { Assert.IsNull(u.Edition); }
                else { Assert.AreEqual(edition, u.Edition); }
                if (isbn.Length > maxIsbn) { isbn = isbn.Substring(0, maxIsbn); }
                Assert.AreEqual(isbn, u.Isbn);
                Assert.AreEqual(borrowable, u.Borrowable);

            }
        }

        /// <summary>
        /// Tests BookManager.Find(Book).
        /// </summary>
        [TestMethod]
        public void TestFind()
        {
            TestableBookManager tbm = new TestableBookManager();
            var e = tbm.Entities;
            var t = tbm.Edited;
            var n = tbm.New;
            var d = tbm.Deleted;
            var m = tbm.Managed;

            // to _entities
            Book b0 = new Book(10, "AA", "TE", null, null, null, "IJ", "SX", false);
            Book b1 = new Book(11, "AA", "TE", null, null, null, "IJ", "SC", false);
            Book b2 = new Book(12, "AA", "TE", null, null, null, "IK", "SX", false);
            Book b3 = new Book(13, "AA", "TE", null, null, null, "IK", "SC", false);
            Book b4 = new Book(14, "AA", "TF", null, null, null, "IJ", "SX", false);
            e.Add(b0);
            e.Add(b1);
            e.Add(b2);
            e.Add(b3);
            e.Add(b4);

            // to _edited
            Book b5 = new Book(15, "AA", "TF", null, null, null, "IJ", "SC", false);
            Book b6 = new Book(16, "AA", "TF", null, null, null, "IK", "SX", false);
            Book b7 = new Book(17, "AA", "TF", null, null, null, "IK", "SC", false);
            Book b8 = new Book(18, "AB", "TE", null, null, null, "IJ", "SX", false);
            Book b9 = new Book(19, "AB", "TE", null, null, null, "IJ", "SC", false);
            t.Add(b5);
            t.Add(b6);
            t.Add(b7);
            t.Add(b8);
            t.Add(b9);

            // to _new
            Book ba = new Book(20, "AB", "TE", null, null, null, "IK", "SX", false);
            Book bb = new Book(21, "AB", "TE", null, null, null, "IK", "SC", false);
            Book bc = new Book(22, "AB", "TF", null, null, null, "IJ", "SX", false);
            Book bd = new Book(23, "AB", "TF", null, null, null, "IJ", "SC", false);
            Book be = new Book(24, "AB", "TF", null, null, null, "IK", "SX", false);
            n.Add(ba);
            n.Add(bb);
            n.Add(bc);
            n.Add(bd);
            n.Add(be);

            // to _deleted
            Book bf = new Book(25, "AB", "TF", null, null, null, "IK", "SC", false);
            d.Add(bf);

            // to _managed
            m.Add(b0);
            m.Add(b1);
            m.Add(b2);
            m.Add(b3);
            m.Add(b4);
            m.Add(b5);
            m.Add(b6);
            m.Add(b7);
            m.Add(b8);
            m.Add(b9);
            m.Add(ba);
            m.Add(bb);
            m.Add(bc);
            m.Add(bd);
            m.Add(be);
            m.Add(bf);


            /*
             * empty / null searches
             */

            // null argument
            Assert.AreEqual(0, tbm.Find(null).Count);

            // every relevant property is ""
            Assert.AreEqual(0, tbm.Find(new Book(1, "", "", "", null, 0, "", "", false)).Count);

            // every relevant property is null
            Assert.AreEqual(0, tbm.Find(new Book(1, null, null, "", null, 0, null, null, false)).Count);


            /*
             * search by single property (4 total)
             */

            // Author = "Ab"
            Assert.AreEqual(7, tbm.Find(new Book(1, "Ab", null, "", null, 0, null, null, false)).Count);

            // Title = "tF"
            Assert.AreEqual(7, tbm.Find(new Book(1, "", "tF", "", null, 0, "", "", false)).Count);

            // SimpleId = "SC"
            Assert.AreEqual(7, tbm.Find(new Book(1, null, null, "", null, 0, null, "SC", false)).Count);

            // Isbn = "ik"
            Assert.AreEqual(7, tbm.Find(new Book(1, null, null, "", null, 0, "ik", "", false)).Count);


            /*
             * search by 2 properties (6 total)
             */

            // Author = "AA", Title = "TE"
            Assert.AreEqual(12, tbm.Find(new Book(1, "AA", "TE", "", null, 0, "", "", false)).Count);

            // Author = "AB", SimpleId = "Sx"
            Assert.AreEqual(11, tbm.Find(new Book(1, "AB", "", "", null, 0, "", "Sx", false)).Count);

            // Author = "AA", Isbn = "ik"
            Assert.AreEqual(11, tbm.Find(new Book(1, "AA", "", "", null, 0, "ik", "", false)).Count);

            // Title = "te", SimpleId = "sC"
            Assert.AreEqual(11, tbm.Find(new Book(1, "", "te", "", null, 0, "", "sC", false)).Count);

            // Title = "TF", Isbn = "IJ"
            Assert.AreEqual(11, tbm.Find(new Book(1, "", "TF", "", null, 0, "IJ", "", false)).Count);

            // SimpleId = "Sx", Isbn = "ij"
            Assert.AreEqual(12, tbm.Find(new Book(1, "", "", "", null, 0, "ij", "Sx", false)).Count);

            /*
             * search by 3 properties (4 total)
             */

            // Author = "Ab", Title = "tE", SimpleId = "sx"
            Assert.AreEqual(13, tbm.Find(new Book(1, "Ab", "tE", "", null, 0, "", "sx", false)).Count);

            // Author = "ab", Title = "tf", Isbn = "Ik"
            Assert.AreEqual(13, tbm.Find(new Book(1, "ab", "tf", "", null, 0, "Ik", "", false)).Count);

            // Author = "a", Isbn = "i", SimpleId = "s"
            Assert.AreEqual(15, tbm.Find(new Book(1, "a", "", "", null, 0, "i", "s", false)).Count);

            // Title = "tf", Isbn = "k", SimpleId = "sc"
            Assert.AreEqual(11, tbm.Find(new Book(1, "", "tf", "", null, 0, "k", "sc", false)).Count);


            /*
             * search by all 4 properties (1 total)
             */

            // Author = "ab", Title = "tf", Isbn = "ik", SimpleId = "sc"
            Assert.AreEqual(14, tbm.Find(new Book(1, "ab", "tf", "", null, 0, "ik", "sc", false)).Count);

        }

        /// <summary>
        /// Tests BookManager.LoadEntities(), PersistEntities().
        /// </summary>
        [TestMethod]
        public void TestLoadPersist()
        {
            // entities to persist
            int entities = 100;
            Random r = new Random();
            TestableBookManager tlmP = new TestableBookManager();

            Int32 id;
            string simpleId;
            var maxSimpleId = BookManager.SIMPLEID_LENGTH;
            string author;
            var maxAuthor = BookManager.MAX_AUTHOR_LENGTH;
            string title;
            var maxTitle = BookManager.MAX_TITLE_LENGTH;
            string publisher;
            var maxPublisher = BookManager.MAX_PUBLISHER_LENGTH;
            DateTime? year;
            ushort? edition;
            string isbn;
            var maxIsbn = BookManager.MAX_ISBN_LENGTH;
            bool borrowable = true;

            // generate random entities
            var randUnicodeStr = new RandomUnicodeString();
            for (int i = 0; i < entities; i++)
            {
                if (r.Next() % 2 == 0) { borrowable ^= true; }
                isbn = randUnicodeStr.GetString(r.Next(maxIsbn * 2));
                if (r.Next() % 3 == 0) { edition = null; }
                else { edition = (ushort) (r.Next(10000) + BookManager.MIN_EDITION_VALUE); }
                if (r.Next() % 2 == 0) { year = new DateTime(r.Next(1, 9999)); }
                else { year = null; }
                publisher = randUnicodeStr.GetString(r.Next(maxPublisher * 2));
                title = randUnicodeStr.GetString(r.Next(maxTitle * 2));
                author = randUnicodeStr.GetString(r.Next(maxAuthor * 2));
                simpleId = randUnicodeStr.GetString(r.Next(maxSimpleId * 2));
                id = i + TestableBookManager.FirstId;

                Book b = new Book(id, author, title, publisher, year, edition, isbn, simpleId, borrowable);

                // place the entity into one of the managed lists, based on i's value
                tlmP.Managed.Add(b);
                switch (i % 4)
                {
                    case 0:
                        tlmP.Entities.Add(b);
                        break;

                    case 1:
                        tlmP.Edited.Add(b);
                        break;

                    case 2:
                        tlmP.New.Add(b);
                        break;

                    case 3:
                        tlmP.Deleted.Add(b);
                        break;
                }
            }

            // persist the entities & wait for the os
            tlmP.PersistEntities();
            Thread.Sleep(1000);

            // load them back
            TestableBookManager tlmL = new TestableBookManager();
            tlmL.LoadEntities();

            // test the results
            //

            // list counts
            Assert.AreEqual(tlmP.Managed.Count - tlmP.Deleted.Count, tlmL.Managed.Count);
            Assert.AreEqual(tlmP.Entities.Count + tlmP.Edited.Count + tlmP.New.Count, tlmL.Entities.Count);
            Assert.AreEqual(0, tlmL.Edited.Count);
            Assert.AreEqual(0, tlmL.New.Count);
            Assert.AreEqual(0, tlmL.Deleted.Count);

            // list values
            foreach (Book l in tlmP.Entities)
            {
                bool found = false;
                foreach (Book o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }
            foreach (Book l in tlmP.Edited)
            {
                bool found = false;
                foreach (Book o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }
            foreach (Book l in tlmP.New)
            {
                bool found = false;
                foreach (Book o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }

            var list = tlmP.Managed.Where(item => !(tlmP.Deleted.Contains(item))).ToList();
            foreach (Book l in tlmL.Managed)
            {
                bool found = false;
                foreach (Book o in list)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }

            // delete the file
            File.Delete(tlmP.GetDatafileName());
        }
    }
}
